﻿using System;
using System.Linq;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Operations;
using Xenta.Utils;

namespace Xenta.Security
{
    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account is active, authenticated and 
    /// the session is opened.
    /// </summary>
    public class IsAuthenticatedSecurityRule : ISecurityRule
    {
        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var session = ctx.Session(uow);
            if(session == null)
                return false;
            return true;
        }

        #endregion
    }

    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account is verified.
    /// </summary>
    /// <remarks>
    /// Checks, if the account associated with the context is not null and has 
    /// the "Verified" flag. If account is not null, but does not have the "Verified" 
    /// flag and the "Security.AccountVerificationRequired" configuration parameter 
    /// is "false", the rule is being met.
    /// </remarks>
    public class IsVerifiedSecurityRule : ISecurityRule
    {
        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var account = ctx.Account(uow);
            if(account == null)
                return false;
            if(account.Flags.HasFlag(AccountFlags.Verified))
                return true;
            return !uow.Execute(new GetConfigParamValueByKey
            {
                Key = Par.In("Core").Of<AccountEntity>().Name("VerificationRequired")
            }).ResultAs<Boolean>();
        }

        #endregion
    }

    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account is in any of the specified 
    /// roles.
    /// </summary>
    public class InAnyRoleSecurityRule : ISecurityRule
    {
        #region Fields

        private readonly string[] _roleCodes;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="roleCodes">The collection of role codes.</param>
        public InAnyRoleSecurityRule(params string[] roleCodes)
        {
            if(roleCodes == null || roleCodes.Length == 0)
                throw new ArgumentNullException();
            _roleCodes = roleCodes;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var account = ctx.Account(uow);
            if(account == null)
                return false;
            return account.Roles.Select(x => x.Code)
                .Intersect(_roleCodes).Count() != 0;
        }

        #endregion
    }

    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account is in all of the specified 
    /// roles.
    /// </summary>
    public class InAllRolesSecurityRule : ISecurityRule
    {
        #region Fields

        private readonly string[] _roleCodes;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="roleCodes">The collection of permission codes.</param>
        public InAllRolesSecurityRule(params string[] roleCodes)
        {
            if(roleCodes == null || roleCodes.Length == 0)
                throw new ArgumentNullException();
            _roleCodes = roleCodes;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var account = ctx.Account(uow);
            if(account == null)
                return false;
            return account.Roles.Select(x => x.Code)
                .Intersect(_roleCodes).Count() == _roleCodes.Length;
        }

        #endregion
    }

    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account has any of the specified 
    /// permissions.
    /// </summary>
    public class HasAnyPermissionSecurityRule : ISecurityRule
    {
        #region Fields

        private readonly string[] _permissionCodes;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="permissionCodes">The collection of role codes.</param>
        public HasAnyPermissionSecurityRule(params string[] permissionCodes)
        {
            if(permissionCodes == null || permissionCodes.Length == 0)
                throw new ArgumentNullException();
            _permissionCodes = permissionCodes;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var account = ctx.Account(uow);
            if(account == null)
                return false;
            return account.Permissions.Select(x => x.Code)
                .Intersect(_permissionCodes).Count() != 0;
        }

        #endregion
    }

    /// <summary>
    /// Represents the security rule, which allows to check, 
    /// if the working account has all of the specified 
    /// permissions.
    /// </summary>
    public class HasAllPermissionsSecurityRule : ISecurityRule
    {
        #region Fields

        private readonly string[] _permissionCodes;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="permissionCodes">The collection of permission codes.</param>
        public HasAllPermissionsSecurityRule(params string[] permissionCodes)
        {
            if(permissionCodes == null || permissionCodes.Length == 0)
                throw new ArgumentNullException();
            _permissionCodes = permissionCodes;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Checks, if the security rule is satisfied.
        /// </summary>
        /// <param name="ctx">The working context.</param>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true if security rule is satisfied; otherwis false.</returns>
        public bool DoesPass(IWorkingContext ctx, IUnitOfWork uow)
        {
            var account = ctx.Account(uow);
            if(account == null)
                return false;
            return account.Permissions.Select(x => x.Code)
                .Intersect(_permissionCodes).Count() == _permissionCodes.Length;
        }

        #endregion
    }
}
